/**
 * Copyright 2016 Bartosz Schiller
 *
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ashlikun.pdfview

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.Paint
import android.graphics.PaintFlagsDrawFilter
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.RectF
import android.util.AttributeSet
import android.util.Log
import android.util.Size
import android.util.SizeF
import android.widget.RelativeLayout
import com.ashlikun.pdfview.exception.PageRenderingException
import com.ashlikun.pdfview.model.PagePart
import com.ashlikun.pdfview.model.PdfBookmark
import com.ashlikun.pdfview.model.PdfLink
import com.ashlikun.pdfview.model.PdfMeta
import com.ashlikun.pdfview.renderer.IPdfRenderer
import com.ashlikun.pdfview.renderer.PdfSystemRenderer
import com.ashlikun.pdfview.renderer.PdfiumRenderer
import com.ashlikun.pdfview.util.PdfViewUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/**
 * 它支持动画、缩放、缓存和滑动。
 *
 *
 * 要想完全理解这门课，你必须了解它的原理：
 * -PDF文档被视为我们总是想绘制所有页面。
 * -问题是我们只画可见的部分。
 * -所有部分的大小都相同，这是因为我们不能中断本机页面呈现，
 * 所以我们需要这些渲染尽可能快，并且能够中断它们
 * 尽快。
 * -当前偏移或当前缩放级别更改时加载零件
 *
 *
 * 重要的 :
 * - DocumentPage = PDF文档的一页。
 * - UserPage = 用户定义的页面。
 * 默认情况下，它们是相同的。但用户可以更改页面顺序
 * 使用{@link#load（DocumentSource，String，int[]）}。在这个
 * 在特定情况下，5的userPage可以引用17的documentPage。
 */
open class PDFView(context: Context, set: AttributeSet?) : RelativeLayout(context, set) {

    var minZoom = DEFAULT_MIN_SCALE
    var midZoom = DEFAULT_MID_SCALE
    var maxZoom = DEFAULT_MAX_SCALE


    //bitmap 缓存管理
    internal val cacheManager by lazy { CacheManager(config) }

    //动画管理器管理所有偏移和缩放动画
    private val animationManager by lazy { com.ashlikun.pdfview.AnimationManager(this) }

    //拖动管理器管理所有触摸事件
    private val dragPinchManager by lazy { DragPinchManager(this, animationManager) }

    //PDF 文件
    internal var pdfFile: PdfFile? = null

    //加载阶段用于解码PDF文档的异步任务
    private var decodingJob: Job? = null

    //始终在后台等待和呈现任务的处理程序
    internal var renderingJob: RenderingJob? = null

    //分页数据加载
    private val pagesLoader: PagesLoader by lazy { PagesLoader(this) }

    //绘制要绘制的对象
    private val paint by lazy { Paint() }

    //当前序列的索引
    var currentPage = 0

    /**
     * 如果您将所有页面并排绘制为最佳宽度，
     * 并且考虑到缩放级别，当前偏移是
     * 此大图中屏幕左边框的位置
     */
    var currentXOffset = 0f
        private set

    /**
     * 如果您将所有页面并排绘制为最佳宽度，
     * 并且考虑到缩放级别，当前偏移是
     * 此大图中屏幕左边框的位置
     */
    var currentYOffset = 0f
        private set

    //缩放级别，始终>=1
    var zoom = 1f
        private set

    //如果PDFView已回收，则为True
    var isRecycled = true
        private set

    //视图的当前状态
    var state = State.DEFAULT
        private set

    //绘制调试对象
    private val debugPaint by lazy {
        Paint().apply {
            style = Paint.Style.STROKE
        }
    }

    //暗色模式
    private var nightMode = false
        set(value) {
            field = value
            if (value) {
                val colorMatrixInverted = ColorMatrix(
                    floatArrayOf(
                        -1f, 0f, 0f, 0f, 255f,
                        0f, -1f, 0f, 0f, 255f,
                        0f, 0f, -1f, 0f, 255f,
                        0f, 0f, 0f, 1f, 0f
                    )
                )
                val filter = ColorMatrixColorFilter(colorMatrixInverted)
                paint.setColorFilter(filter)
            } else {
                paint.setColorFilter(null)
            }
        }


    //PDF 渲染核心
    private lateinit var pdfRenderer: IPdfRenderer


    //配置
    val config by lazy { PDFViewConfig() }

    //当前应该显示的区域
    var currentPageRange: IntRange? = null

    //抗锯齿过滤
    private val antialiasFilter by lazy {
        PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG)
    }

    private var isCanLoad = false

    /**
     * 构建初始视图
     */
    init {
        if (!isInEditMode) {
            setWillNotDraw(false)
            setRendererManage(RendererManage.SYSTEM)
        }
    }

    fun setRendererManage(type: RendererManage) {
        if (type == RendererManage.SYSTEM) {
            pdfRenderer = PdfSystemRenderer(context, config)
        } else {
            pdfRenderer = PdfiumRenderer(context, config)
        }
    }

    /**
     * 加载内容
     */
    fun load() {
        if (config.docSource == null) return
        isCanLoad = true
        if (!isRecycled) {
            recycle()
        }
        // 开始解码文档,如果没有宽度和高度，则等待onSizeChanged 调用
        if (width > 0 && height > 0) {
            isRecycled = false
            cacheManager.cacheBitmapSize(config.cacheBitmapSize)
            decodingJob = GlobalScope.launch(Dispatchers.IO) {
                runCatching {
                    pdfRenderer.createRenderer(config.docSource!!, config.password)
                    PdfFile(pdfRenderer, Size(width, height), config)
                }.getOrNull().also {
                    withContext(Dispatchers.Main) {
                        if (it != null) loadComplete(it) else loadError(Throwable("load error"))
                    }
                }
            }
        }
    }

    /**
     * 转到给定的页面。
     */
    fun jumpTo(page: Int, withAnimation: Boolean = false) {
        var page = page
        if (pdfFile == null) return
        page = pdfFile!!.determineValidPageNumberFrom(page)
//        val offset = if (page == 0) 0f else -pdfFile!!.getPageOffset(page, zoom)
        val offset = -snapOffsetForPage(page)
        if (config.isVertical) {
            if (withAnimation) {
                animationManager.startYAnimation(currentYOffset, offset)
            } else {
                moveTo(currentXOffset, offset)
            }
        } else {
            if (withAnimation) {
                animationManager.startXAnimation(currentXOffset, offset)
            } else {
                moveTo(offset, currentYOffset)
            }
        }
        showPage(page)
    }

    fun showPage(pageNb: Int) {
        var pageNb = pageNb
        if (isRecycled) return
        // 检查页码并使
        // UserPages和DocumentPages之间的区别
        pageNb = pdfFile!!.determineValidPageNumberFrom(pageNb)
        currentPage = pageNb
        loadPages()
        if (config.scrollHandle != null && !documentFitsView()) {
            config.scrollHandle!!.setPageNum(currentPage + 1)
        }
        config.callbacks.onPageChange?.invoke(currentPage, pdfFile!!.pagesCount)
    }

    /**
     * 获取当前位置作为文档长度与可见区域的比率。
     * 0表示文档开始可见，1表示文档结束可见
     *
     * @return 0和1之间的偏移
     */
    var positionOffset: Float
        get() {
            val offset = if (config.isVertical) -currentYOffset / (pdfFile!!.getDocLen(zoom) - height)
            else -currentXOffset / (pdfFile!!.getDocLen(zoom) - width)

            return PdfViewUtil.limit(offset, 0f, 1f)
        }
        set(progress) {
            setPositionOffset(progress, true)
        }

    /**
     * @param progress   必须介于0和1之间
     * @param moveHandle 是否移动滚动手柄
     * @see PDFView.positionOffset
     */
    fun setPositionOffset(progress: Float, moveHandle: Boolean) {
        if (config.isVertical) {
            moveTo(currentXOffset, (-pdfFile!!.getDocLen(zoom) + height) * progress, moveHandle)
        } else {
            moveTo((-pdfFile!!.getDocLen(zoom) + width) * progress, currentYOffset, moveHandle)
        }
        loadPageByOffset()
    }

    fun stopFling() {
        animationManager.stopFling()
    }

    val pageCount: Int
        get() = pdfFile?.pagesCount ?: 0

    fun onPageError(ex: PageRenderingException) {
        config.callbacks.onPageError?.invoke(ex.page, ex.cause)
        Log.e(TAG, "Cannot open page " + ex.page, ex.cause)
    }

    //回收对象
    fun recycle() {
        //动画停止
        animationManager.stopAll()
        //拖拽手柄
        dragPinchManager.disable()
        // 停止渲染任务
        renderingJob?.stop()
        renderingJob = null
        //打开文件任务
        decodingJob?.cancel()
        decodingJob = null
        // 清除缓存
        cacheManager.recycle()
        currentPageRange = null
        config.scrollHandle?.destroyLayout()
        pdfFile?.dispose()
        pdfFile = null
        currentYOffset = 0f
        currentXOffset = 0f
        zoom = 1f
        isRecycled = true
        state = State.DEFAULT
    }

    /**
     * 手柄投掷动画
     */
    override fun computeScroll() {
        super.computeScroll()
        if (isInEditMode) {
            return
        }
        animationManager.computeFling()
    }

    override fun onDetachedFromWindow() {
        recycle()
        super.onDetachedFromWindow()
    }


    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        if (isCanLoad) {
            load()
        }
        if (isInEditMode || state != State.SHOWN) {
            return
        }
        // 计算位于视图中心的点相对于大条形图的位置
        val centerPointInStripXOffset = -currentXOffset + oldw * 0.5f
        val centerPointInStripYOffset = -currentYOffset + oldh * 0.5f
        val relativeCenterPointInStripXOffset: Float
        val relativeCenterPointInStripYOffset: Float
        if (config.isVertical) {
            relativeCenterPointInStripXOffset = centerPointInStripXOffset / pdfFile!!.maxPageWidth
            relativeCenterPointInStripYOffset = centerPointInStripYOffset / pdfFile!!.getDocLen(zoom)
        } else {
            relativeCenterPointInStripXOffset = centerPointInStripXOffset / pdfFile!!.getDocLen(zoom)
            relativeCenterPointInStripYOffset = centerPointInStripYOffset / pdfFile!!.maxPageHeight
        }
        animationManager.stopAll()
        pdfFile!!.recalculatePageSizes(Size(w, h))
        if (config.isVertical) {
            currentXOffset = -relativeCenterPointInStripXOffset * pdfFile!!.maxPageWidth + w * 0.5f
            currentYOffset = -relativeCenterPointInStripYOffset * pdfFile!!.getDocLen(zoom) + h * 0.5f
        } else {
            currentXOffset = -relativeCenterPointInStripXOffset * pdfFile!!.getDocLen(zoom) + w * 0.5f
            currentYOffset = -relativeCenterPointInStripYOffset * pdfFile!!.maxPageHeight + h * 0.5f
        }
        moveTo(currentXOffset, currentYOffset)
        loadPageByOffset()
    }

    override fun canScrollHorizontally(direction: Int): Boolean {
        if (pdfFile == null) {
            return true
        }
        if (config.isVertical) {
            if (direction < 0 && currentXOffset < 0) {
                return true
            } else if (direction > 0 && currentXOffset + toCurrentScale(pdfFile!!.maxPageWidth) > width) {
                return true
            }
        } else {
            if (direction < 0 && currentXOffset < 0) {
                return true
            } else if (direction > 0 && currentXOffset + pdfFile!!.getDocLen(zoom) > width) {
                return true
            }
        }
        return false
    }

    override fun canScrollVertically(direction: Int): Boolean {
        if (pdfFile == null) {
            return true
        }
        if (config.isVertical) {
            if (direction < 0 && currentYOffset < 0) {
                return true
            } else if (direction > 0 && currentYOffset + pdfFile!!.getDocLen(zoom) > height) {
                return true
            }
        } else {
            if (direction < 0 && currentYOffset < 0) {
                return true
            } else if (direction > 0 && currentYOffset + toCurrentScale(pdfFile!!.maxPageHeight) > height) {
                return true
            }
        }
        return false
    }

    override fun onDraw(canvas: Canvas) {
        if (isInEditMode) {
            return
        }
        // 正如我在这个类javadoc中所说的，我们可以将这个画布看作一个巨大的
        //我们在其上绘制所有图像的条带。我们实际上只绘制渲染的
        //当然是部分，但我们将它们呈现在这个巨大的地方

        // 这就是Canvas.translate（x，y）变得非常有用的地方。
        // 情况就是这样 :
        //  _______________________________________________
        // |   			 |					 			   |
        // | the actual  |					The big strip  |
        // |	canvas	 | 								   |
        // |_____________|								   |
        // |_______________________________________________|
        //
        // 如果渲染的部分位于条形图的右下角
        //我们可以画，但看不见，因为画布不够大。

        // 但是，如果我们在绘制对象之前在画布上调用translate（-X，-Y） :
        //  _______________________________________________
        // |   			  					  _____________|
        // |   The big strip     			 |			   |
        // |		    					 |	the actual |
        // |								 |	canvas	   |
        // |_________________________________|_____________|
        //
        // 该对象将在画布上。
        //该技术在该方法中被大量使用，并且允许
        //渲染零件时屏幕位置的抽象。

        // 绘制背景
        if (config.isAntialiasing) {
            canvas.drawFilter = antialiasFilter
        }
        val bg = background
        if (bg == null) {
            canvas.drawColor(if (nightMode) Color.BLACK else Color.WHITE)
        } else {
            bg.draw(canvas)
        }
        if (isRecycled) {
            return
        }
        if (state != State.SHOWN) {
            return
        }

        // 在绘制任何元素之前移动画布
        val currentXOffset = currentXOffset
        val currentYOffset = currentYOffset
        canvas.translate(currentXOffset, currentYOffset)
        // 绘制组件
        if (PdfViewUtil.DEBUG_MODE) PdfViewUtil.logi("当前绘制的页数区间: $currentPageRange")
        currentPageRange?.forEach { page ->
            cacheManager.getFindZoom(page, zoom)?.let { it1 -> drawPart(canvas, it1) }
        }
        currentPageRange?.forEach { page ->
            drawWithListener(canvas, page, config.callbacks.onDrawAll)
        }
        drawWithListener(canvas, currentPage, config.callbacks.onDraw)
        // 恢复画布位置
        canvas.translate(-currentXOffset, -currentYOffset)
    }

    private fun drawWithListener(canvas: Canvas, page: Int, listener: OnDrawListener?) {
        if (listener != null) {
            val translateX: Float
            val translateY: Float
            if (config.isVertical) {
                translateX = 0f
                translateY = pdfFile!!.getPageOffset(page, zoom)
            } else {
                translateY = 0f
                translateX = pdfFile!!.getPageOffset(page, zoom)
            }
            canvas.translate(translateX, translateY)
            val size = pdfFile!!.getPageSize(page)
            listener.invoke(canvas, toCurrentScale(size.width), toCurrentScale(size.height), page)
            canvas.translate(-translateX, -translateY)
        }
    }

    /**
     * 在画布上绘制给定的PagePart
     */
    private fun drawPart(canvas: Canvas, part: PagePart) {
        // 可能看起来很奇怪，但要避开很多电话
        val renderedBitmap = part.bitmap
        if (renderedBitmap.isRecycled) {
            return
        }

        // 移动到目标页面
        var localTranslationX = 0f
        var localTranslationY = 0f
        val size = pdfFile!!.getPageSize(part.page)
        if (config.isVertical) {
            localTranslationY = pdfFile!!.getPageOffset(part.page, zoom)
            val maxWidth = pdfFile!!.maxPageWidth
            localTranslationX = toCurrentScale(maxWidth - size.width) / 2
        } else {
            localTranslationX = pdfFile!!.getPageOffset(part.page, zoom)
            val maxHeight = pdfFile!!.maxPageHeight
            localTranslationY = toCurrentScale(maxHeight - size.height) / 2
        }
        canvas.translate(localTranslationX, localTranslationY)
        val srcRect = Rect(0, 0, renderedBitmap.width, renderedBitmap.height)
        val offsetX = 0f
        val offsetY = 0f
        val width = toCurrentScale(size.width)
        val height = toCurrentScale(size.height)

        // 如果我们对这个矩形使用浮点值，那么
        //页面各部分之间可能存在的间隙，尤其是在
        //缩放级别高。
        val dstRect = RectF(
            offsetX.toInt().toFloat(), offsetY.toInt().toFloat(),
            (offsetX + width).toInt().toFloat(),
            (offsetY + height).toInt().toFloat()
        )

        // 检查屏幕中是否有位图
        val translationX = currentXOffset + localTranslationX
        val translationY = currentYOffset + localTranslationY
        if (translationX + dstRect.left >= getWidth() || translationX + dstRect.right <= 0 || translationY + dstRect.top >= getHeight() || translationY + dstRect.bottom <= 0) {
            canvas.translate(-localTranslationX, -localTranslationY)
            return
        }
        canvas.drawBitmap(renderedBitmap, srcRect, dstRect, paint)
        if (PdfViewUtil.DEBUG_MODE) {
            debugPaint.color = if (part.page % 2 == 0) Color.RED else Color.BLUE
            canvas.drawRect(dstRect, debugPaint)
        }

        // 恢复画布位置
        canvas.translate(-localTranslationX, -localTranslationY)
    }

    /**
     * 加载屏幕中心周围的所有零件，
     * 考虑X和Y偏移、缩放级别，以及
     * 显示的当前页面
     */
    fun loadPages() {
//        Util.loge("loadPages")
        if (pdfFile == null || renderingJob == null) {
            return
        }

        //取消所有当前任务
        renderingJob?.cleanTask()
        pagesLoader.loadPages()
        redraw()
    }

    /**
     * 加载PDF时调用
     */
    internal fun loadComplete(pdfFile: PdfFile) {
        state = State.LOADED
        this.pdfFile = pdfFile
        renderingJob = RenderingJob(this)
        renderingJob!!.start()
        config.scrollHandle?.setupLayout(this)
        config.scrollHandle?.hideDelayed()
        dragPinchManager.enable()
        config.callbacks.onLoadComplete?.invoke(pdfFile.pagesCount)
        jumpTo(currentPage, false)
    }

    fun loadError(t: Throwable) {
        state = State.ERROR
        // 存储引用，因为回调将在recycle（）方法中清除
        val onErrorListener = config.callbacks.onError
        recycle()
        invalidate()
        onErrorListener?.invoke(t) ?: Log.e("PDFView", "load pdf error", t)
    }

    fun redraw() {
        invalidate()
    }

    /**
     * 当渲染任务结束并且
     * 新创建了一个PagePart。
     * @param part 创建的PagePart。
     */
    fun onBitmapRendered(part: PagePart) {
//        Util.logi("onBitmapRendered " + part.page)
        // 首次渲染零件时
        if (state == State.LOADED) {
            state = State.SHOWN
            config.callbacks.onRender?.invoke(pdfFile!!.pagesCount)
        }
        cacheManager.set(part)
        redraw()
    }

    /**
     * 移动到给定的X和Y偏移，但提前检查
     * 以确保不要走出大条。
     *
     * @param offsetX    要用作屏幕左边框的大条带X偏移。
     * @param offsetY    要用作屏幕右边框的大条形Y偏移。
     * @param moveHandle 是否移动滚动手柄
     */
    fun moveTo(offsetX: Float, offsetY: Float, moveHandle: Boolean = true) {
        var offsetX = offsetX
        var offsetY = offsetY
        if (config.isVertical) {
            // 检查X偏移
            val scaledPageWidth = toCurrentScale(pdfFile!!.maxPageWidth)
            if (scaledPageWidth < width) {
                offsetX = width / 2 - scaledPageWidth / 2
            } else {
                if (offsetX > 0) {
                    offsetX = 0f
                } else if (offsetX + scaledPageWidth < width) {
                    offsetX = width - scaledPageWidth
                }
            }

            // 检查Y偏移
            val contentHeight = pdfFile!!.getDocLen(zoom)
            if (contentHeight < height) { // 整个文档高度在屏幕上可见
                offsetY = (height - contentHeight) / 2
            } else {
                if (offsetY > 0) { // 顶部可见
                    offsetY = 0f
                } else if (offsetY + contentHeight < height) { // 底部可见
                    offsetY = -contentHeight + height
                }
            }
        } else {
            // 检查Y偏移
            val scaledPageHeight = toCurrentScale(pdfFile!!.maxPageHeight)
            if (scaledPageHeight < height) {
                offsetY = height / 2 - scaledPageHeight / 2
            } else {
                if (offsetY > 0) {
                    offsetY = 0f
                } else if (offsetY + scaledPageHeight < height) {
                    offsetY = height - scaledPageHeight
                }
            }

            // 检查X偏移
            val contentWidth = pdfFile!!.getDocLen(zoom)
            if (contentWidth < width) { // whole document width visible on screen
                offsetX = (width - contentWidth) / 2
            } else {
                if (offsetX > 0) { // left visible
                    offsetX = 0f
                } else if (offsetX + contentWidth < width) { // right visible
                    offsetX = -contentWidth + width
                }
            }
        }
        currentXOffset = offsetX
        currentYOffset = offsetY
        val positionOffset = positionOffset
        if (moveHandle && config.scrollHandle != null && !documentFitsView()) {
            config.scrollHandle!!.setScroll(positionOffset)
        }
        config.callbacks.onPageScroll?.invoke(currentPage, positionOffset)
        redraw()
    }

    fun loadPageByOffset() {
        if (0 == pdfFile?.pagesCount ?: 0) {
            return
        }
        val offset: Float
        val screenCenter: Float
        if (config.isVertical) {
            offset = currentYOffset
            screenCenter = height.toFloat() / 2
        } else {
            offset = currentXOffset
            screenCenter = width.toFloat() / 2
        }
        val page = pdfFile!!.getPageAtOffset(-(offset - screenCenter), zoom)
        if (page >= 0 && page <= pdfFile!!.pagesCount - 1 && page != currentPage) {
            showPage(page)
        } else {
            loadPages()
        }
    }

    /**
     * 动画制作到当前SnapPolicy最近的捕捉位置
     */
    fun performPageSnap() {
        if (!config.isPageSnap || pdfFile == null || pdfFile!!.pagesCount == 0) {
            return
        }
        val centerPage = findFocusPage(currentXOffset, currentYOffset)
        val edge = findSnapEdge(centerPage)
        if (edge == SnapEdge.NONE) {
            return
        }
        val offset = snapOffsetForPage(centerPage, edge)
        if (config.isVertical) {
            animationManager.startYAnimation(currentYOffset, -offset)
        } else {
            animationManager.startXAnimation(currentXOffset, -offset)
        }
    }

    /**
     * 显示指定页面时查找要捕捉的边缘
     */
    fun findSnapEdge(page: Int): SnapEdge {
        if (!config.isPageSnap || page < 0) {
            return SnapEdge.NONE
        }
        val currentOffset = if (config.isVertical) currentYOffset else currentXOffset
        val offset = -pdfFile!!.getPageOffset(page, zoom)
        val length = if (config.isVertical) height else width
        val pageLength = pdfFile!!.getPageLength(page, zoom)
        return if (length >= pageLength) {
            SnapEdge.CENTER
        } else if (currentOffset >= offset) {
            SnapEdge.START
        } else if (offset - pageLength > currentOffset - length) {
            SnapEdge.END
        } else {
            SnapEdge.NONE
        }
    }

    /**
     * 获取要移动到的偏移量以便相对居中
     */
    fun snapOffsetForPage(pageIndex: Int, edge: SnapEdge = findSnapEdge(pageIndex)): Float {
        var offset = pdfFile!!.getPageOffset(pageIndex, zoom)
        val length = (if (config.isVertical) height else width).toFloat()
        val pageLength = pdfFile!!.getPageLength(pageIndex, zoom)
        if (edge == SnapEdge.CENTER) {
            offset = offset - length / 2f + pageLength / 2f
        } else if (edge == SnapEdge.END) {
            offset = offset - length + pageLength
        }
        return offset
    }

    fun findFocusPage(xOffset: Float, yOffset: Float): Int {
        val currOffset = if (config.isVertical) yOffset else xOffset
        val length = (if (config.isVertical) height else width).toFloat()
        // 确保能找到第一页和最后一页
        if (currOffset > -1) {
            return 0
        } else if (currOffset < -pdfFile!!.getDocLen(zoom) + length + 1) {
            return pdfFile!!.pagesCount - 1
        }
        // 否则在中心查找页面
        val center = currOffset - length / 2f
        return pdfFile!!.getPageAtOffset(-center, zoom)
    }

    /**
     * @return 如果单个页面在滚动方向上填满整个屏幕，则为true
     */
    fun pageFillsScreen(): Boolean {
        if (pdfFile == null || pdfFile!!.pagesCount == 0) return false
        val start = -pdfFile!!.getPageOffset(currentPage, zoom)
        val end = start - pdfFile!!.getPageLength(currentPage, zoom)
        return if (config.isVertical) start > currentYOffset && end < currentYOffset - height
        else start > currentXOffset && end < currentXOffset - width
    }

    /**
     * 相对移动到当前位置。
     *
     * @param dx 您要应用的X差异。
     * @param dy 要应用的Y差。
     * @see .moveTo
     */
    fun moveRelativeTo(dx: Float, dy: Float) {
        moveTo(currentXOffset + dx, currentYOffset + dy)
    }

    /**
     * 更改缩放级别
     */
    fun zoomTo(zoom: Float) {
        this.zoom = zoom
    }

    /**
     * 相对于轴心点更改缩放级别。
     * 它将调用moveTo（）以确保给定点保持不变
     * 在屏幕中间。
     *
     * @param zoom  缩放级别。
     * @param pivot 屏幕上应该保留的点。
     */
    fun zoomCenteredTo(zoom: Float, pivot: PointF) {
        val dzoom = zoom / this.zoom
        zoomTo(zoom)
        var baseX = currentXOffset * dzoom
        var baseY = currentYOffset * dzoom
        baseX += pivot.x - pivot.x * dzoom
        baseY += pivot.y - pivot.y * dzoom
        moveTo(baseX, baseY)
    }

    fun zoomCenteredRelativeTo(dzoom: Float, pivot: PointF) {
        zoomCenteredTo(zoom * dzoom, pivot)
    }

    /**
     * 检查整个文档是否可以显示在屏幕上，不包括缩放
     *
     * @return 如果整个文档可以同时显示，则为true，否则为false
     */
    fun documentFitsView(): Boolean {
        val len = pdfFile!!.getDocLen(1f)
        return if (config.isVertical) {
            len < height
        } else {
            len < width
        }
    }

    fun fitToWidth(page: Int) {
        if (state != State.SHOWN) {
            Log.e(TAG, "Cannot fit, document not rendered yet")
            return
        }
        zoomTo(width / pdfFile!!.getPageSize(page).width)
        jumpTo(page)
    }

    fun getPageSize(pageIndex: Int): SizeF {
        return if (pdfFile == null) {
            SizeF(0f, 0f)
        } else pdfFile!!.getPageSize(pageIndex)
    }

    fun toRealScale(size: Float): Float {
        return size / zoom
    }

    fun toCurrentScale(size: Float): Float {
        return size * zoom
    }

    val isZooming: Boolean
        get() = zoom != minZoom

    fun resetZoom() {
        zoomTo(minZoom)
    }

    fun resetZoomWithAnimation() {
        zoomWithAnimation(minZoom)
    }

    fun zoomWithAnimation(centerX: Float, centerY: Float, scale: Float) {
        animationManager.startZoomAnimation(centerX, centerY, zoom, scale)
    }

    fun zoomWithAnimation(scale: Float) {
        animationManager.startZoomAnimation((width / 2).toFloat(), (height / 2).toFloat(), zoom, scale)
    }

    /**
     * 获取给定偏移处的页码
     *
     * @param positionOffset 0和1之间的滚动偏移
     * @return 给定偏移处的页码，从0开始
     */
    fun getPageAtPositionOffset(positionOffset: Float): Int {
        return pdfFile!!.getPageAtOffset(pdfFile!!.getDocLen(zoom) * positionOffset, zoom)
    }


    /**
     * 如果未加载文档，则返回null
     */
    val documentMeta: PdfMeta?
        get() = pdfFile?.metaData

    /**
     * 在加载文档之前将为空
     */
    val tableOfContents: List<PdfBookmark>
        get() = pdfFile?.bookmarks.orEmpty()

    /**
     * 在加载文档之前将为空
     */
    fun getLinks(page: Int): List<PdfLink> = pdfFile?.getPageLinks(page).orEmpty()

    companion object {
        private val TAG = PDFView::class.java.simpleName
        const val DEFAULT_MAX_SCALE = 3.0f
        const val DEFAULT_MID_SCALE = 1.75f
        const val DEFAULT_MIN_SCALE = 1.0f
    }
}
